package com.allenja.eleviewer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.PolygonManager;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureInfo;
import com.threed.jpct.TextureManager;
import com.threed.jpct.Virtualizer;
import com.threed.jpct.World;
import com.threed.jpct.util.MemoryHelper;

import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.Camera.CameraInfo;
import android.location.Location;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Resources;
import android.util.FloatMath;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

public class CameraViewActivity extends Activity {

	private CameraPreview cameraPreview;
	private Camera camera = null;
	private int numberOfCameras = 0;
	private int cameraCurrentlyLocked = 0;
	private int defaultCameraId = 0;
	
	private static String rendererLocationMode = "Explorer";
	
	// Used to handle pause and resume...
	private static CameraViewActivity master = null;
	
	// Renderer vars
	private GLSurfaceView glView;
	private MyRenderer renderer = null;
	private FrameBuffer fb = null;
	private World world = null;;
	private RGBColor back = null;
	private Resources res = null;
	public static Texture esri_color_scale = null;
	private static String modelDirectoryPath = "";
	private Object3D[] models = null;
	private static Texture font = null;
	private static com.threed.jpct.Camera cam = null;
	private Light sun = null;
	private static final float MAX_Z = 555.413025f; // srtm 1314 // JCU 555.413025f;
	private static final float MIN_Z = 2.7523f; // srtm -15 // JCU 2.7523f;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().setFormat(PixelFormat.TRANSLUCENT);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		
		glView = new GLSurfaceView(getApplication());
		glView.setEGLContextClientVersion(2);
		glView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
		glView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
		glView.setZOrderOnTop(true);
		renderer = new MyRenderer();
		glView.setRenderer(renderer);
		glView.setPreserveEGLContextOnPause(true);
		
		if (rendererLocationMode == "Explorer") {			
			setContentView(glView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
		}
		
		else if (rendererLocationMode == "GPS") {
			cameraPreview = new CameraPreview(this);
			setContentView(cameraPreview, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
		
			glView = new MyGLSurfaceView(this);
			addContentView(glView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
			
			numberOfCameras = Camera.getNumberOfCameras();
			CameraInfo cameraInfo = new CameraInfo();
			
			for (int i = 0; i < numberOfCameras; i++) {
				Camera.getCameraInfo(i, cameraInfo);
				
				if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
					defaultCameraId = i;
				}
			}
		}
		
		if (master != null) {
			copy(master);
		}
		
		if (modelDirectoryPath == "") {
			Toast.makeText(this, "Please choose your model directory before starting the camera view (under settings)", Toast.LENGTH_LONG).show();
			finish();
		}
		else {
			Toast.makeText(this, "Starting the sensors, GPS (if required) and creating the model view. Please wait...", Toast.LENGTH_LONG).show();
		}
	}

	@Override
	protected void onStart() {
		Logger.log("onStart: CameraViewActivity");
		super.onStart();
	}
	
	@Override
	protected void onPause() {
		Logger.log("onPause: CameraViewActivity");
		super.onPause();
		glView.onPause();
		
		if (getLocationMode() == "GPS") {
        	
        	if (camera != null) {
            cameraPreview.setCamera(null);
            camera.release();
            camera = null;
        	}
        }        
	}

    @Override
    protected void onResume() {
    	Logger.log("onResume: CameraViewActivity");
        super.onResume();
        glView.onResume();
        
        if (getLocationMode() == "GPS") {
        	
        	camera = Camera.open();
        	cameraCurrentlyLocked = defaultCameraId;
        	cameraPreview.setCamera(camera);
        }        
    }
    
    protected void onStop() {
    	Logger.log("onStop: CameraViewActivity");
		super.onStop();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_camera_view, menu);
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		
		case R.id.switch_cam:
			if (numberOfCameras == 1) {
				AlertDialog.Builder builder = new AlertDialog.Builder(this);
				builder.setMessage(this.getString(R.string.camera_alert)).setNeutralButton("Close", null);
				AlertDialog alert = builder.create();
				alert.show();
				return true;
			}

			if (camera != null) {
				camera.stopPreview();
				cameraPreview.setCamera(null);
				camera.release();
				camera = null;
			}

			camera = Camera.open((cameraCurrentlyLocked + 1) % numberOfCameras);
			cameraCurrentlyLocked = (cameraCurrentlyLocked + 1)	% numberOfCameras;
			cameraPreview.switchCamera(camera);

			camera.startPreview();
			return true;
			
		case R.id.instructions:
			AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
			alertDialogBuilder.setMessage(this.getString(R.string.instructions_alert)).setNeutralButton("Close", null);
			AlertDialog instructionsAlert = alertDialogBuilder.create();
			instructionsAlert.show();
			return true;
			
		default:
			return super.onOptionsItemSelected(item);
		}
	}
	
	private void copy(Object src) {
		try {
			Logger.log("Copying data from master Activity!");
			Field[] fs = src.getClass().getDeclaredFields();
			for (Field f : fs) {
				f.setAccessible(true);
				f.set(this, f.get(src));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public String getLocationMode() {
		// get location mode (explorer or gps)
		return rendererLocationMode;
	}
	
	public static void setLocationMode(String locationMode) {
		// set location mode (explorer or gps)
		rendererLocationMode = locationMode;
	}
	
	public static void setModelDirectoryPath(String settingsViewChosenDir) {
		modelDirectoryPath = settingsViewChosenDir;
	}

	public static com.threed.jpct.Camera getWorldCam() {
		return cam;
	}

	public static void setWorldCam(com.threed.jpct.Camera cam) {
		CameraViewActivity.cam = cam;
	}

	public static Texture getFont() {
		return font;
	}

	public void setFont(Texture font) {
		CameraViewActivity.font = font;
	}
	
	@SuppressLint("FloatMath")
	public class MyRenderer implements GLSurfaceView.Renderer, SensorEventListener {
		// renderer
		private TextureManager tm = TextureManager.getInstance();
		private Virtualizer virtualizer = new Virtualizer();
		private boolean stop = false;
		private String modelViewMode = "Polygons";
		// frames per second
		private long time = System.currentTimeMillis();
		private int fps = 0;
		private int lfps = 0;
		// touch control
		private int touchMode = 0;
		private final int NONE = 0;
		private final int DRAG = 0;
		private final int ZOOM = 0;
		private float oldDistance = 250;
		private float newDistance = 0;
		private int tapCount = 0;
		private long tapStartTime = 0;
		private long tapDuration = 0;
		// sensor control
		private SensorManager sensorManager;
		private Sensor accelSensor = null;
		private Sensor magSensor = null;
		private float[] rotationMatrix = new float[9];
		private float[] accelData = new float[3];
		private float[] bufferedAccelData = new float[3];
		private float[] magnetData = new float[3];
		private float[] bufferedMagnetData = new float[3];
		final static float rad2deg = (float) (180.0f / Math.PI);
		// CONSTANTS
		private static final double MODEL_ORIGIN_X = 145.66726645776;// srtm 145.62583361 //JCU 145.66726645776;
		private static final double MODEL_ORIGIN_Y = -16.8281367352939;// srtm -17.0313619906 // JCU -16.8281367352939;
		//private static final float MAX_Y = 3000f;
		//private static final float MAX_Z = 555.413025f; // srtm 1314 // JCU 555.413025f;
		//private static final float MIN_Z = 2.7523f; // srtm -15 // JCU 2.7523f;
		private static final int MAX_TAP_DURATION = 500;
		private static final double SCALE = 0.00000927; // srtm 30m = 0.00027778 // JCU 1m = 0.00000927; // DD to metre
		// gps control
		private Location currentLocation = null;
		private boolean currentLocationHasChanged = false;
		// variables for gps mode
		private float newCamX = 0;
		private float newCamY = 0;
		private float newCamZ = 0;

		public MyRenderer() {	
			// initialise accelerometer and magnet sensor
			setSensorManager((SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE));
			accelSensor = getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
			magSensor = getSensorManager().getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
			
			// configuration for OpenGL
			Config.maxPolysVisible = 100000;
			Config.farPlane = 1000000;
			Config.glTransparencyMul = 0.1f;
			Config.glTransparencyOffset = 0.1f;
			Config.useVBO = true;
			
			// configuration for textures
			Texture.defaultToMipmapping(true);
			Texture.defaultTo4bpp(true);
			tm.setVirtualizer(virtualizer);
			virtualizer.setContext(getApplicationContext());
		}

		public void stop() {
			stop = true;
			
			// unregister the sensors
			sensorManager.unregisterListener(this);
		}
		
		public void pause() {
			
		}
		
		public void resume() {
			stop = false;
		}

		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			Logger.log("MyRenderer surface created");
		}

		public void onSurfaceChanged(GL10 gl, int w, int h) {
			// dispose framebuffer if not null
			if (fb != null) {
				fb.dispose();
			}
			
			// create new framebuffer
			fb = new FrameBuffer(w, h);
			
			if (master == null) {				
				// get application resources
		    	res = getResources();
		    	esri_color_scale = new Texture(res.openRawResource(R.raw.esri_color_scale));
		    	
		    	// create a new empty world with ambient light
		    	world = new World();
		    	world.setAmbientLight(100, 100, 100);
		    	
		    	// check if textures are already in the texture manager and add if not
				if (!tm.containsTexture("esri_color_scale")) {
					tm.addTexture("esri_color_scale", CameraViewActivity.esri_color_scale);
				}
				
				tm.virtualize(CameraViewActivity.esri_color_scale);
		    	
		    	File chosenModelDir = new File(modelDirectoryPath);
				
				if (chosenModelDir.isDirectory()) {
					File[] listOfModelFiles = chosenModelDir.listFiles();
					
					if (listOfModelFiles != null) {
						int i = 0;
						models = new Object3D[listOfModelFiles.length];
						
						for (File file : listOfModelFiles) {
							Logger.log("Model file # " + i + ": " + file);
							
							try {
								InputStream dem = new FileInputStream(file);
								models[i] = Loader.loadSerializedObject(dem);
								dem.close();
								i++;
							} 
							catch (FileNotFoundException e) {
								e.printStackTrace();
							}
							catch (IOException e) {
								e.printStackTrace();
							}
						}
						
						for (Object3D obj : models) {
							PolygonManager objPolyManager = obj.getPolygonManager();
							
							// for each polygon set texture according to normalised vertex elevations
							for (int j = 0; j < objPolyManager.getMaxPolygonID(); j++) {
								float u0 = 0, u1 = 0, u2 = 0;
								
								for (int k = 0; k < 3; k++) {
									SimpleVector vertex = objPolyManager.getTransformedVertex(j, k);								
									float normalisedHeight = (vertex.z - MIN_Z)/(MAX_Z - MIN_Z);
									
									if (k == 0) {
										u0 = normalisedHeight;
									}
									else if (k == 1) {
										u1 = normalisedHeight;
									}
									else if (k == 2) {
										u2 = normalisedHeight;
									}
								}
								
								TextureInfo ti = new TextureInfo(tm.getTextureID("esri_color_scale"), u0, u0, u1, u1, u2, u2);
								objPolyManager.setPolygonTexture(j, ti);
							}
							world.addObject(obj);
						}
					}
				}
				
				if (rendererLocationMode.equals("GPS")) {
					// set model transparency
					for (int i = 0; i < world.getSize(); i++) {
						world.getObject(i).setTransparency(8); // 80%
					}	
				}
				
				// initialise texture for numbers
				font = new Texture(res.openRawResource(R.drawable.numbers));
				font.setMipmap(false);
				
				// get the worlds camera
				cam = world.getCamera();
				
				// set the position of the camera in the world
				cam.setPosition(1500,1500,800); // srtm 9500, 7500, 800 // JCU 1500,1500,800
				
				// build all objects in world
				world.buildAllObjects();
				
				// create a sun and set intensity
				sun = new Light(world);
				sun.setIntensity(200, 200, 200);
				
				// set the position of the sun in the world
				SimpleVector sv = new SimpleVector();
				sv.set(world.getObject(0).getTransformedCenter());
				sv.y -= 300;
				sv.x -= 100;
				sv.z += 200;
				sun.setPosition(sv);
				
				// free up some memory
				MemoryHelper.compact();
				
				if (master == null) {
					Logger.log("Saving master Activity!");
					master = CameraViewActivity.this;
				}
				
				if (modelViewMode.equals("Polygons")) {				
					// register the sensors
					getSensorManager().registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME);
					getSensorManager().registerListener(this, magSensor,	SensorManager.SENSOR_DELAY_GAME);
				}
			}
		}


		public void onDrawFrame(GL10 gl) {
			try {
				if (!stop) {
					
					// set background color and alpha
					back = new RGBColor(0, 0, 0, 0); // black with alpha
					
					// smooth sensor data
					rootMeanSquareBuffer(bufferedAccelData, accelData);
					rootMeanSquareBuffer(bufferedMagnetData, magnetData);
					
					// get rotation matrix from device and set camera orientation accordingly
					boolean success = SensorManager.getRotationMatrix(rotationMatrix, null, bufferedAccelData, bufferedMagnetData);
					if (success) {
						setCamBackBuffer();
					}

					fb.clear(back);
					world.renderScene(fb);
					world.draw(fb);

					if (rendererLocationMode.equals("GPS")) {
						// Logger.log("GPS mode");

						if (currentLocation != null || currentLocationHasChanged) {
							
							// assign new camera X and Y from gps 
							newCamX = (float) ((currentLocation.getLongitude() - MODEL_ORIGIN_X) / SCALE);
							newCamY = (float) ((currentLocation.getLatitude() - MODEL_ORIGIN_Y) / SCALE);
							newCamZ = (float) currentLocation.getAltitude();
							
							// set new camera position
							CameraViewActivity.getWorldCam().setPosition(newCamX, newCamY, newCamZ);
							
							// draw world and Decimal Degree coordinates to the screen
							String newCamXString = String.valueOf(newCamX);
							String[] newCamXStringParts = newCamXString.split("\\.");
							String newCamXStringDegree = newCamXStringParts[0];
							String newCamXStringDecimalDegree = newCamXStringParts[1];

							blitNumber(Integer.parseInt(newCamXStringDegree), 50, 5);
							blitNumber(Integer.parseInt(newCamXStringDecimalDegree), 100, 5);

							String newCamYString = String.valueOf(newCamY);
							String[] newCamYStringParts = newCamYString.split("\\.");
							String newCamYStringDegree = newCamYStringParts[0];
							String newCamYStringDecimalDegree = newCamYStringParts[1];

							blitNumber(Integer.parseInt(newCamYStringDegree), 175, 5);
							blitNumber(Integer.parseInt(newCamYStringDecimalDegree), 225, 5);

							blitNumber((int) newCamZ, 300, 5);

							String currentLongitudeString = String.valueOf(currentLocation.getLongitude());
							String[] currentLongitudeStringParts = currentLongitudeString.split("\\.");
							String currentLongitudeStringDegree = currentLongitudeStringParts[0];
							String currentLongitudeStringDecimalDegree = currentLongitudeStringParts[1];

							blitNumber(Integer.parseInt(currentLongitudeStringDegree), 375, 5);
							blitNumber(Integer.parseInt(currentLongitudeStringDecimalDegree), 425, 5);

							String currentLatitudeString = String.valueOf(currentLocation.getLatitude());
							String[] currentLatitudeStringParts = currentLatitudeString.split("\\.");
							String currentLatitudeStringDegree = currentLatitudeStringParts[0];
							String currentLatitudeStringDecimalDegree = currentLatitudeStringParts[1];

							blitNumber(Integer.parseInt(currentLatitudeStringDegree), 500, 5);
							blitNumber(Integer.parseInt(currentLatitudeStringDecimalDegree), 550, 5);
							
							currentLocationHasChanged = false;
						} 
						
						else if (currentLocation == null) {
							
							// set camera position to model origin and 800m
							newCamX = world.getObject(0).getOrigin().x;
							newCamY = world.getObject(0).getOrigin().y;
							newCamZ = 800f;

							CameraViewActivity.getWorldCam().setPosition(newCamX, newCamY, newCamZ);
							
							// draw world coordinates to the screen
							String camXString = String.valueOf(newCamX);
							String[] camXStringParts = camXString.split("\\.");
							String camXStringDegree = camXStringParts[0];
							String camXStringDecimalDegree = camXStringParts[1];

							blitNumber(Integer.parseInt(camXStringDegree), 50, 5);
							blitNumber(Integer.parseInt(camXStringDecimalDegree), 100, 5);

							String camYString = String.valueOf(newCamY);
							String[] camYStringParts = camYString.split("\\.");
							String camYStringDegree = camYStringParts[0];
							String camYStringDecimalDegree = camYStringParts[1];

							blitNumber(Integer.parseInt(camYStringDegree), 175, 5);
							blitNumber(Integer.parseInt(camYStringDecimalDegree), 225, 5);

							blitNumber((int) newCamZ, 300, 5);
						}
					} 
					
					else if (rendererLocationMode.equals("Explorer")) {
						// Logger.log("Explorer mode");
						
						// draw world coordinates to the screen
						String camXString = String.valueOf(CameraViewActivity.getWorldCam().getPosition().x);
						String[] camXStringParts = camXString.split("\\.");
						String camXStringDegree = camXStringParts[0];
						String camXStringDecimalDegree = camXStringParts[1];

						blitNumber(Integer.parseInt(camXStringDegree), 50, 5);
						blitNumber(Integer.parseInt(camXStringDecimalDegree), 100, 5);

						String camYString = String.valueOf(CameraViewActivity.getWorldCam().getPosition().y);
						String[] camYStringParts = camYString.split("\\.");
						String camYStringDegree = camYStringParts[0];
						String camYStringDecimalDegree = camYStringParts[1];

						blitNumber(Integer.parseInt(camYStringDegree), 175, 5);
						blitNumber(Integer.parseInt(camYStringDecimalDegree), 225, 5);

						blitNumber((int) CameraViewActivity.getWorldCam().getPosition().z, 300, 5);
					}
					
					// draw frame rate to the screen
					blitNumber(lfps, 5, 5);
					fb.display();
					
					// update frame rate
					if (System.currentTimeMillis() - time >= 1000) {
						lfps = fps;
						fps = 0;
						time = System.currentTimeMillis();
					}
					fps++;
				} 
				
				else {
					if (fb != null) {
						fb.dispose();
						fb = null;
					}
				}

			} catch (Exception e) {
				e.printStackTrace();
				Logger.log("Drawing thread terminated!", Logger.MESSAGE);
			}
		}

		private void setCamBackBuffer() {		
			// set camera back buffer to current device orientation
			float[] result = new float[9];
			SensorManager.remapCoordinateSystem(rotationMatrix,	SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_MINUS_X, result);
			com.threed.jpct.Matrix mResult = new com.threed.jpct.Matrix();
			copyMatrix(result, mResult);
			CameraViewActivity.getWorldCam().setBack(mResult);
		}

		private void blitNumber(int number, int x, int y) {		
			// get number from numbers.jpg (font) and draw to screen
			if (CameraViewActivity.getFont() != null) {
				String sNum = Integer.toString(number);

				for (int i = 0; i < sNum.length(); i++) {
					char cNum = sNum.charAt(i);
					int iNum = cNum - 48;
					//fb.blit(font, iNum * 5, 0, x, y, 5, 9, FrameBuffer.TRANSPARENT_BLITTING);				
					
					fb.blit(CameraViewActivity.getFont(), iNum * 5, 0, x, y, 5, 9, 10, 18, 100000, false);
					x += 10;
				}
			}
		}

		private void copyMatrix(float[] src, com.threed.jpct.Matrix dest) {		
			// copy the rotation matrix from sensors
			dest.setRow(0, src[0], src[1], src[2], 0);
			dest.setRow(1, src[3], src[4], src[5], 0);
			dest.setRow(2, src[6], src[7], src[8], 0);
			dest.setRow(3, 0f, 0f, 0f, 1f);
		}

		public void handleGLSurfaceTouchEvent(MotionEvent event) {		
			// handle touch events
			int action = event.getAction();

			switch (action & MotionEvent.ACTION_MASK) {

			case MotionEvent.ACTION_DOWN:
				tapStartTime = System.currentTimeMillis();
				tapCount++;
				touchMode = DRAG;
				break;

			case MotionEvent.ACTION_POINTER_DOWN:
				oldDistance = getTouchSpacing(event);

				if (oldDistance > 0) {
					touchMode = ZOOM;
				}
				break;

			case MotionEvent.ACTION_UP:
				long time = System.currentTimeMillis() - tapStartTime;
				tapDuration = tapDuration + time;
				if (tapCount == 2 && tapDuration <= MAX_TAP_DURATION) {
					tapCount = 0;
					tapDuration = 0;
					tapStartTime = 0;
					CameraViewActivity.getWorldCam().setPosition(world.getObject(0).getOrigin().x, world.getObject(0).getOrigin().y, 2000f);
				}
				
				else if (tapCount > 2) {
					tapCount = 0;
					tapDuration = 0;
					tapStartTime = 0;
				}
				
				touchMode = NONE;
				oldDistance = 250;
				break;

			case MotionEvent.ACTION_POINTER_UP:
				touchMode = NONE;
				oldDistance = 250;
				break;

			case MotionEvent.ACTION_MOVE:
				if (event.getPointerCount() > 1 && touchMode == ZOOM) {
					newDistance = getTouchSpacing(event);
					
					if (oldDistance < newDistance && rendererLocationMode.equals("Explorer")) {
						if (CameraViewActivity.getWorldCam().getPosition().z >= 100 && CameraViewActivity.getWorldCam().getPosition().z <= 5000) {
							CameraViewActivity.getWorldCam().moveCamera(com.threed.jpct.Camera.CAMERA_MOVEIN, 25);
						}

						else if (CameraViewActivity.getWorldCam().getPosition().z < 100) {
							CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 100);
						}

						else if (CameraViewActivity.getWorldCam().getPosition().z > 5000) {
							CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 5000);
						}
					}

					else if (oldDistance > newDistance && rendererLocationMode.equals("Explorer")) {
						if (CameraViewActivity.getWorldCam().getPosition().z >= 100 && CameraViewActivity.getWorldCam().getPosition().z <= 5000) {
							CameraViewActivity.getWorldCam().moveCamera(com.threed.jpct.Camera.CAMERA_MOVEOUT, 25);
						}

						else if (CameraViewActivity.getWorldCam().getPosition().z < 100) {
							CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 100);
						}

						else if (CameraViewActivity.getWorldCam().getPosition().z > 5000) {
							CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 5000);
						}
					}
				}

				else if (touchMode == DRAG) {
					break;
				}
			}
		}

		public void handleLocationChanged(Location location) {		
			// handle gps location changed
			currentLocation = location;
			currentLocationHasChanged = true;
			//Logger.log("Current location in MyRenderer: " + currentLocation);
		}

		private float getTouchSpacing(MotionEvent event) {		
			// get the distance between fingers when touched
			float x = event.getX(0) - event.getX(1);
			float y = event.getY(0) - event.getY(1);
			return FloatMath.sqrt(x * x + y * y);
		}

		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// handle sensors accuracy changed
			// Logger.log("onAccuracyChanged: MyRenderer");
		}

		public void onSensorChanged(SensorEvent event) {
			// handle sensor changed
			loadNewSensorData(event);
		}

		private void loadNewSensorData(SensorEvent event) {
			// clone sensor data
			final int type = event.sensor.getType();

			switch (type) {
			
			case Sensor.TYPE_ACCELEROMETER:
				accelData = event.values.clone();
				break;

			case Sensor.TYPE_MAGNETIC_FIELD:
				magnetData = event.values.clone();
				break;
			}
		}

		private void rootMeanSquareBuffer(float[] target, float[] values) {
			// smooth sensor data
			final float amplification = 200.0f;
			float buffer = 20.0f;

			target[0] += amplification;
			target[1] += amplification;
			target[2] += amplification;
			values[0] += amplification;
			values[1] += amplification;
			values[2] += amplification;

			target[0] = FloatMath.sqrt((target[0] * target[0] * buffer + values[0]
					* values[0])
					/ (1 + buffer));
			target[1] = FloatMath.sqrt((target[1] * target[1] * buffer + values[1]
					* values[1])
					/ (1 + buffer));
			target[2] = FloatMath.sqrt((target[2] * target[2] * buffer + values[2]
					* values[2])
					/ (1 + buffer));

			target[0] -= amplification;
			target[1] -= amplification;
			target[2] -= amplification;
			values[0] -= amplification;
			values[1] -= amplification;
			values[2] -= amplification;
		}
		
		public void setModelViewMode(String mode) {
			modelViewMode = mode;
		}

		public SensorManager getSensorManager() {
			return sensorManager;
		}

		public void setSensorManager(SensorManager sensorManager) {
			this.sensorManager = sensorManager;
		}
	}
}
